/*
 * ============================================================================
 * GNU Lesser General Public License
 * ============================================================================
 *
 * Servoy - Smart Technology For Smart Clients.
 * Copyright � 1997-2008 Servoy BV http://www.servoy.com
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307, USA.
 * 
 * Servoy B.V.
 * Algolweg 9A
 * 3821 BG Amersfoort
 * The Netherlands 
 * http://www.servoy.com
 */
package com.servoy.r3.plugins;

import java.awt.print.PrinterException;
import java.awt.print.PrinterJob;
import java.io.ByteArrayInputStream;
import java.io.FileOutputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import javax.print.PrintService;

import net.sf.jasperreports.engine.JasperPrint;
import net.sf.jasperreports.engine.JasperPrintManager;
import net.sf.jasperreports.view.JasperViewer;

import com.servoy.j2db.scripting.IScriptObject;
import com.servoy.j2db.util.Debug;
import com.servoy.j2db.util.Utils;

/**
 * IScriptObject impl.
 * 
 * @author drichardson 
 * For external library dependancies, see:
 * http://www.jasperforge.org/jaspersoft/opensource/business_intelligence/jasperreports/requirements.html
 */
public class JasperReportsProvider implements IScriptObject {
    private JasperReportsPlugin plugin;

    private IJasperReportsService jasperReportService = null;


    private static final int PARAMETER = 1;

    private static final int TOOLTIP = 2;

    private static final int EXAMPLE = 3;

    private static final String PROPERTIES[][][] = {
	    // methodname, parameters, tooltip, example
	    {
		{ "jasperReport" },
		{ "serverName", "reportFileName", "exportFileName / boolean showPrintDialog / printerName", "outputFormat", "Object parameters", "[locale]" },
		{ "Execute a Report" }, {} },
	    { { "writeFile" }, { "reportFileName", "JSFile reportFile" }, { "Store a reportFile on the Server" },
		    { "plugins.jasperPluginRMI.writeFile('myCustomerReport.jasper',reportFile;\n" } },
	    { { "readFile" }, { "reportFileName" }, { "Retrieve a reportFile from the Server" },
		    { "var reportFile = plugins.jasperPluginRMI.readFile('myCustomerReport.jasper');\n" } },
	    { { "jasperCompile" }, { "reportFileName", "boolean forceRecompile" }, { "Compile a reportFile on the Server" },
		    { "var success = plugins.jasperPluginRMI.jasperCompile('myCustomerReport.jasper',true);\n" } } };

    JasperReportsProvider(JasperReportsPlugin p) {
	plugin = p;
    }

    public String[] getProperty(String methodName, int prop) {
	for (int i = 0; i < PROPERTIES.length; i++) {
	    String as[][] = PROPERTIES[i];
	    if (as[0][0].equals(methodName)) {
		return as[prop];
	    }
	}
	return null;
    }

    public boolean isDeprecated(String methodName) {
	if (methodName == null)
	    return false;
	else if ("".equals(methodName)) {
	    return true;
	}
	return false;
    }

    public String[] getParameterNames(String methodName) {
	return getProperty(methodName, PARAMETER);
    }

    public String getSample(String methodName) {
	if (methodName == null)
	    return null;
	else if ("jasperReport".equals(methodName)) {
	    StringBuffer retval = new StringBuffer();
	    retval.append("/* To view the result of the customers report in the Jasper Report viewer in the SmartClient or as PDF in the WebClient\n");
	    retval.append(" * Note: the parameters argument is used to send additional parameters into the report. For example:\n");
	    retval.append(" * {pcustomerid: forms.customers.customer_id} to send just 1 parameter called pcustomerid, which contains the value of dataprovider customer_id in the selected record on the customers form\n");
	    retval.append(" * The parameters argument is an Object, which can be instantiated in two ways:\n");
	    retval.append(" * var o = new Object();\n");
	    retval.append(" * o.pcustomerid = forms.customers.customer_id;\n");
	    retval.append(" * or:\n");
	    retval.append(" * var o = {pcustomerid: forms.customers.customer_id};\n");
	    retval.append(" */\n");
	    retval.append("plugins.jasperPluginRMI.jasperReport(forms.customers.controller.getServerName(),'myCustomerReport.jasper',null,'view',{pcustomerid: forms.customers.customer_id});\n");
	    retval.append("\n");
	    retval.append("/* To request a report in a different Language than the current language of the client, it's possible to specify a Locale string as the locale argument. For example: 'en' or 'es' or 'nl'\n");
	    retval.append(" * When the locale argument is not specified, the report will be in the current langauge of the Client\n");
	    retval.append(" * i18n keys of Servoy can be used inside Jasper Reports using the $R{i18n-key} notation\n");
	    retval.append(" */\n");
	    retval.append("//plugins.jasperPluginRMI.jasperReport(forms.customers.controller.getServerName(),'myCustomerReport.jasper',null,'view',{pcustomerid: forms.customers.customer_id},'nl');\n");
	    retval.append("\n");
	    retval.append("/* To print the result of the customers report in the SmartClient,\n");
	    retval.append(" * the third parameter can contain the name of the printer to which the report needs to be printed\n");
	    retval.append(" * or can contain true (boolean value) to show a printdialog before printing\n");
	    retval.append(" * if null is specified, it will print without showing the print dialog to the default printer\n");
	    retval.append(" * Note: In the WebClient a PDF will be pushed to the Client when the outputType is specified as 'print'\n");
	    retval.append(" */\n");
	    retval.append("//plugins.jasperPluginRMI.jasperReport(forms.customers.controller.getServerName(),'myCustomerReport.jasper',null,'print',{pcustomerid: forms.customers.customer_id});\n");
	    retval.append("\n");
	    retval.append("/* To generate the report in the specified output format and save the result to 'myReport.html' in the root of the c-drive:\n");
	    retval.append(" * Supported output formats are: html, pdf, excel and xml\n");
	    retval.append(" * Note: in the WebClient, the file will be saved serverside, so the specified path needs to be valid serverside\n");
	    retval.append(" */\n");
	    retval.append("//plugins.jasperPluginRMI.jasperReport(forms.customers.controller.getServerName(),'myCustomerReport.jasper','c:/myReport.html','html',{pcustomerid: forms.customers.customer_id});\n");
	    retval.append("\n");
	    retval.append("/* Jasper Reports supports queries with IN operators through the following notation: X${IN,columnName,parameterName} like 'select * from customers where X$(IN,customer_id,pcustomeridlist)\n");
	    retval.append(" * When using this notation, the pcustomeridlist parameter needs to contain one or more values in the following way:\n");
	    retval.append(" */\n");
	    retval.append("//var idlist = new Array()\n");
	    retval.append("//idlist[0] = 1\n");
	    retval.append("//idlist[1] = 26\n");
	    retval.append("//plugins.jasperPluginRMI.jasperReport(forms.customers.controller.getServerName(),'myCustomerReport.jasper',null,'view',{pcustomeridlist: idlist});\n");
	    return retval.toString();
	} else {
	    String[] as = getProperty(methodName, EXAMPLE);
	    if (as != null)
		return as[0];
	}
	return null;
    }

    public String getToolTip(String methodName) {
	String[] as = getProperty(methodName, TOOLTIP);
	if (as != null)
	    return as[0];
	return null;
    }

    public Class[] getAllReturnedTypes() {
	return null;
    }

    public void saveByteArrayToFile(String filename, byte[] buffertje) throws Exception {
	FileOutputStream fos = new FileOutputStream(filename);
	fos.write(buffertje);
	fos.flush();
	fos.close();
    }

    /**
     * Fix for bug ID 6255588 from Sun bug database
     * @param job print job that the fix applies to
     */
    public static void initPrinterJobFields(PrinterJob job) {
	Class clazz = job.getClass();
	try {
	    Class printServiceClass = Class.forName("javax.print.PrintService");
	    Method method = clazz.getMethod("getPrintService", (Class[]) null);
	    Object printService = method.invoke(job, (Object[]) null);
	    method = clazz.getMethod("setPrintService", new Class[] { printServiceClass });
	    method.invoke(job, new Object[] { printService });
	} catch (NoSuchMethodException e) {
	} catch (IllegalAccessException e) {
	} catch (InvocationTargetException e) {
	} catch (ClassNotFoundException e) {
	}
    }

    public boolean setPrintService(PrinterJob printJob, String printer) throws Exception {
	PrintService[] service = PrinterJob.lookupPrintServices();
	// enable if on list otherwise error
	boolean match = false;
	int count = service.length;
	try {
	    for (int i = 0; i < count; i++) {
		if (service[i].getName().indexOf(printer) != -1) {
		    printJob.setPrintService(service[i]);
		    i = count;
		    match = true;
		}
	    }
	} catch (PrinterException e) {
	    Debug.error(e);
	    throw new Exception(e.getMessage());
	}
	return match;
    }

    public byte[] js_jasperReport(String dbalias, String report, Object arg, String type, Object parameters) throws Exception {
	return js_jasperReport(dbalias, report, arg, type, parameters, null);
    }

    public byte[] js_jasperReport(String dbalias, String report, Object arg, String type, Object parameters, String localeString) throws Exception {

	Map params = (Map) new JSArgumentsUnwrap().unwrapJSObject(parameters);
	if (params == null)
	    params = new HashMap();

	boolean showPrintDialog = false;
	String file = "";
	if (arg instanceof String) {
	    file = arg.toString();
	} else if (arg instanceof Boolean) {
	    showPrintDialog = Utils.getAsBoolean(arg);
	} else {
		if (arg != null)
			file = arg.toString(); //To support passing in a JSFile object
	}

	Debug.trace("JasperTrace: JasperReport initialize");
	// create if not yet created
	if (jasperReportService == null) {
	    try {
		jasperReportService = (IJasperReportsService) plugin.getIClientPluginAccess().getServerService("servoy.IJasperReportService");
	    } catch (Exception e) {
		Debug.error(e);
		throw new Exception("Jasper Exception: Cannot connect to service-server");
	    }
	}

	// incase the server is not started in developer
	if (jasperReportService != null) {
	    try {
		Debug.trace("JasperTrace: JasperReport service found");

		if (parameters != null) {
		    for (Iterator iter = params.keySet().iterator(); iter.hasNext();) {
			Object key = iter.next();
			Object value = params.get(key);
			if (value instanceof ByteArrayInputStream) {
			    byte[] buf = null;
			    ByteArrayInputStream is = (ByteArrayInputStream) value;
			    is.read(buf);
			}
		    }
		}

		/*
		 * This plugin is backwards compatible up to Servoy 2.2 Only
		 * since 3.5.1, the application.getReleaseNumber() API was added
		 * to determine properly which version you are running To be
		 * compatible back to Servoy 2.2, the code below is to test
		 * which functions are available in the Servoy Instance. This
		 * code needs to be compiled against Servoy 3.5.3 or higher,
		 * since Servoy 3.5.3 implements the i18n getResourceBundle API
		 */
		int applicationType = -1;
		boolean supportsWebClientAPI = false;
		boolean supportsResourcebundleAPI = false;

		try {
		    applicationType = plugin.getIClientPluginAccess().getApplicationType(); /*
		     * Support
		     * added
		     * in
		     * 3.0,
		     * together
		     * with
		     * WebClient
		     */
		} catch (Exception e1) {/* Ignore */
		}

		try {
		    Class.forName("com.servoy.j2db.server.headlessclient.IWebClientPluginAccess"); /*
		     * Supported
		     * as
		     * of
		     * 3.5
		     * RC 7
		     */
		    supportsWebClientAPI = true;
		} catch (ClassNotFoundException e) {/* Ignore */
		}

		try {
		    if (plugin.getIClientPluginAccess().getReleaseNumber() >= 516) {/*
		     * Proper
		     * ResourceBundle
		     * Support added
		     * in 3.5.3
		     */
			supportsResourcebundleAPI = true;
		    }
		    supportsWebClientAPI = true;
		} catch (NoSuchMethodError e2) {/* Ignore */
		}

		if (supportsResourcebundleAPI) {
		    new JasperReportsI18NHandler().appendI18N(params, applicationType == 5 /*
		     * Hardcoded
		     * integer:
		     * IClientPluginAccess.WEB_CLIENT
		     * not
		     * supported
		     * in
		     * 3.0
		     */, plugin.getIClientPluginAccess(), localeString);
		}

		byte[] jsp = null;
		// Report Viewer requested, or Report print in WC requested
		if (type.toLowerCase().startsWith("view") || (type.toLowerCase().startsWith("print") && applicationType == 5)) {
		    if (applicationType == 5 /*
		     * Hardcoded integer:
		     * IClientPluginAccess.WEB_CLIENT
		     * not supported in 3.0
		     */) {
			if (supportsWebClientAPI) {
			    jsp = jasperReportService.jasperReport(dbalias, report, "pdf", params);
			    new JasperReportsWebViewer().show(plugin.getIClientPluginAccess(), jsp, file);
			} else {
			    Debug.log("JasperLog: ReportViewer requested in WebClient (not supported < Servoy 3.5.1)");
			}
		    } else {
			JasperPrint jp = jasperReportService.jasperViewer(dbalias, report, params);
			JasperViewer jasperviewer = new JasperViewer(jp, false);
			if (jp != null && jp.getPages() != null && jp.getPages().size() > 0) {
			    jasperviewer.setVisible(true);
			    jasperviewer.setDefaultCloseOperation(JasperViewer.DISPOSE_ON_CLOSE);
                        } else {
                            jasperviewer.dispose();
                        }
		    }
		}
		// Printing only supported in SC. Printing in WC handled
		// previously though PDF push
		else if (type.toLowerCase().startsWith("print")) {

		    // Fill the report and get the JasperPrint instance
		    JasperPrint jp = jasperReportService.jasperViewer(dbalias, report, params);

		    // Shows print dialog before printing
		    if (showPrintDialog || file == null || file == "true") {
			JasperPrintManager.printReport(jp, true);
		    }
		    // Skips print dialog
		    else if (file.equalsIgnoreCase("false") || file.equalsIgnoreCase("default")) {
			JasperPrintManager.printReport(jp, false);
		    }
		    // Assumes parameter file contains a printerName
		    else {
			Debug.trace("JasperTrace: printer: " + file);
			PrinterJob printJob = PrinterJob.getPrinterJob();
			// fix for bug ID 6255588 from Sun bug database
			initPrinterJobFields(printJob);
			/*
			 * or onother workaround try {
			 * printerJob.setPrintService(printerJob.getPrintService()); }
			 * catch (PrinterException e) {}
			 */

			if (setPrintService(printJob, file)) {
			    JasperReportsPrinter.printPages(jp, printJob);
			} else {
			    Debug.trace("JasperTrace: unable to specify printer: " + file);
			}
		    }
		} else {
		    jsp = jasperReportService.jasperReport(dbalias, report, type, params);
		    saveByteArrayToFile(file, jsp);
		}
		Debug.trace("JasperTrace: JasperReport finished");
		return jsp;
	    } catch (Exception e) {
		Debug.error(e);
		throw new Exception(e.getMessage());
	    }
	}
	Debug.error("JasperTrace: Jasper Exception: No service running");
	throw new Exception("JasperTrace: Jasper Exception: No service running");
    }

    public boolean js_jasperCompile(String report, boolean forceRecompile) throws Exception {

	Debug.trace("JasperTrace: JasperCompile initialize");
	boolean compiled = false;

	connectJasperService();

	try {
	    Debug.trace("JasperTrace: JasperCompile starting");
	    compiled = jasperReportService.jasperCompile(report, forceRecompile);
	    Debug.trace("JasperTrace: JasperCompile finished");
	} catch (Exception e) {
	    Debug.error(e);
	    throw new Exception(e.getMessage());
	}

	return compiled;
    }

    public boolean js_writeFile(String filenm, Object obj) throws Exception {

	connectJasperService();

	try {
	    Debug.trace("JasperTrace: JasperWriteFile starting");
	    boolean b = jasperReportService.writeFile(filenm, obj);
	    Debug.trace("JasperTrace: JasperWriteFile finished");
	    return b;
	} catch (Exception e) {
	    Debug.error(e);
	    return false;
	}
    }

    public byte[] js_readFile(String filenm) throws Exception {

	byte[] b = null;

	connectJasperService();

	try {
	    Debug.trace("JasperTrace: JasperReadFile starting");
	    b = jasperReportService.readFile(filenm);
	    Debug.trace("JasperTrace: JasperReadFile finished");
	} catch (Exception e) {
	    Debug.error(e);
	    throw new Exception(e.getMessage());
	}
	return b;
    }

    public String js_getReportDirectory() throws Exception {

	String directory = null;

	connectJasperService();

	try {
	    Debug.trace("JasperTrace: getReportDirectory starting");
	    directory = jasperReportService.getReportDirectory();
	    Debug.trace("JasperTrace: getReportDirectory finished");
	} catch (Exception e) {
	    Debug.error(e);
	    throw new Exception(e.getMessage());
	}

	return directory;
    }

    public void js_setReportDirectory(String jasperDirectorie) throws Exception {

	connectJasperService();

	try {
	    Debug.trace("JasperTrace: setReportDirectory starting");
	    jasperReportService.setReportDirectory(jasperDirectorie);
	    Debug.trace("JasperTrace: setReportDirectory finished");
	} catch (Exception e) {
	    Debug.error(e);
	    throw new Exception(e.getMessage());
	}
    }

    public void connectJasperService() throws Exception {

	Debug.trace("JasperTrace: service connection initialize");
	// create if not yet created
	if (jasperReportService == null) {
	    try {
		jasperReportService = (IJasperReportsService) plugin.getIClientPluginAccess().getServerService("servoy.IJasperReportService");
	    } catch (Exception ex) {
		Debug.error(ex);
		throw new Exception("JasperTrace: Jasper Exception: Cannot connect to service-server");
	    }
	}

	// incase the server is not started in developer
	if (jasperReportService == null) {
	    System.err.println("JasperTrace: Jasper Exception: No service running");
	    throw new Exception("JasperTrace: Jasper Exception: No service running");
	}
	Debug.trace("JasperTrace: service connection found");
    }
 
}
